package login

import (
	"gitee.com/tomatomeatman/golang-repository/bricks2/model/msgentity"

	"encoding/json"
	"strings"
	"sync"
	"time"

	Log "github.com/cihub/seelog"

	"gitee.com/tomatomeatman/golang-repository/bricks2/function/data/md5util"
	"gitee.com/tomatomeatman/golang-repository/bricks2/function/fileutil"
	"gitee.com/tomatomeatman/golang-repository/bricks2/utils/app"
)

type LoginServer struct {
	app.ServiceBaseFunc
}

var (
	mapLoginUser                      = map[string]*LoginUser{} //存储用户登录信息的集合
	dLastClearTime       int64        = 0                       //最后清理的时间戳
	serverSessionTimeout int64        = 600                     //超时时间(秒)
	appMd5Key            string                                 //访问密钥
	LoginServerLock      sync.RWMutex                           //读写锁
	//sDbName              string                              //数据库名
)

/**
 * 初始化
 */
func init() {
	serverSessionTimeout = app.ReadConfigKey("App", "SessionTimeout", serverSessionTimeout).(int64) * 10

	appMd5Key = app.ReadConfigKey("App", "Md5Key", "8888").(string)

	//sDbName = app.ReadConfigKey("DbVariables", "", "").(string)

	go LoginServer{}.loadCache() //读取缓存
}

/**
 * 登录
 * @param nameOrNo
 * @param pass
 * @param owner 用户来源表
 * @param device 设备类型,1:PC,2:手机,3:平板,4.....
 * @param resultInfo 是否返回用户信息
 * @return
 */
func (ls LoginServer) In(nameOrNo, pass, owner string, device, resultInfo int) *msgentity.MsgEntity {
	ls.checkOutTime() //检查超时的登录用户,超时则剔除集合

	if strings.TrimSpace(nameOrNo) == "" {
		return msgentity.Err(8000, "登录名不能为空！")
	}

	if strings.TrimSpace(pass) == "" {
		return msgentity.Err(8001, "密码不能为空！")
	}

	if strings.TrimSpace(owner) == "" {
		return msgentity.Err(8007, "用户来源不能为空！")
	}

	me := ls.findBynameOrNo(nameOrNo, owner)

	if !me.Success {
		return msgentity.Err(8002, "查询失败，请稍后重试！")
	}

	list := me.Data.([]LoginUser)

	if len(list) < 1 {
		return msgentity.Err(8003, "相关登录名的用户不存在！")
	}

	if len(list) != 1 {
		return msgentity.Err(8004, "存在重名用户，请使用工号登录！")
	}

	loginUser := list[0]
	if md5util.Lower(pass, appMd5Key) != loginUser.Pass {
		Log.Debug("用户“", loginUser.Name, "”登录时密码错误")
		return msgentity.Err(8005, "用户密码错误！")
	}

	if loginUser.State != 1 {
		return msgentity.Err(8006, "登录受限，请联系管理员！")
	}

	if device < 1 {
		device = 1
	}

	//LoginLogService{}.AddLog(ctx, nameOrNo)//AOP已经实现

	//--剔除旧的登录用户--//
	LoginServerLock.Lock() //加锁

	Id := loginUser.Id
	for key, obj := range mapLoginUser {
		if obj.Id != Id {
			continue
		}

		if obj.Device != device {
			continue
		}

		delete(mapLoginUser, key)
		Log.Debug("旧登录用户'", Id, "'因同账号登录被剔除登录集合!")
		break
	}

	token := md5util.Lower(Id, "|", owner, "|", time.Now().Unix(), "|", device, "|", appMd5Key)
	loginUser.Token = token //分配的Token
	loginUser.Device = device
	loginUser.Owner = owner
	loginUser.LastDate = time.Now().Unix()
	mapLoginUser[token] = &loginUser

	LoginServerLock.Unlock() //解锁

	ls.changeCache() //修改缓存

	if resultInfo != 1 {
		return msgentity.Success(token, "登录成功")
	}

	result := loginUser.Clone()
	result.Pass = ""     //必须清除
	result.PassWork = "" //必须清除

	return msgentity.Success(result, "登录成功")
}

/**
 * 登出
 * @param token
 * @return
 */
func (ls LoginServer) Out(token string) *msgentity.MsgEntity {
	if strings.TrimSpace(token) == "" {
		return msgentity.Err(8002, "令牌参数缺失")
	}

	LoginServerLock.RLock()         //加读锁
	defer LoginServerLock.RUnlock() //解读锁

	loginUser, ok := mapLoginUser[token]
	if !ok {
		ls.changeCache() //修改缓存
		return msgentity.Success(8001, "已经登出")
	}

	Log.Debug("登录用户'", loginUser.Id, "'主动进行了登出操作!")
	ls.checkOutTime() //检查超时的登录用户,超时则剔除集合

	return msgentity.Success(8999, "登出成功")
}

/**
 * 判断Token是否已经登录
 * @param token 令牌
 * @return
 */
func (ls LoginServer) Check(token string) *msgentity.MsgEntity {
	if strings.TrimSpace(token) == "" {
		return msgentity.Err(8002, "令牌参数缺失")
	}

	LoginServerLock.RLock()         //加读锁
	defer LoginServerLock.RUnlock() //解读锁

	loginUser, ok := mapLoginUser[token]
	if !ok {
		return msgentity.Err(8001, "该登录令牌信息失效")
	}

	loginUser.LastDate = time.Now().Unix()

	ls.checkOutTime() //检查超时的登录用户,超时则剔除集合

	return msgentity.Success(8999, "该登录令牌信息有效")
}

/**
 * 登录心跳操作,Token存在则更新并返回true,没有则返回false
 * @param token 令牌
 * @return
 */
func (ls LoginServer) Heartbeat(token string) *msgentity.MsgEntity {
	if strings.TrimSpace(token) == "" {
		return msgentity.Err(8001, "令牌参数缺失")
	}

	LoginServerLock.RLock()         //加读锁
	defer LoginServerLock.RUnlock() //解读锁

	loginUser, ok := mapLoginUser[token]
	if !ok {
		return msgentity.Err(8002, "令牌已失效,请重新登录")
	}

	loginUser.LastDate = time.Now().Unix()

	ls.checkOutTime() //检查超时的登录用户,超时则剔除集合

	return msgentity.Success(8999, "令牌心跳已经更新")
}

/**
 * 取登录信息
 * @param token 令牌
 * @return
 */
func (ls LoginServer) GetLogin(token string) *msgentity.MsgEntity {
	if strings.TrimSpace(token) == "" {
		return msgentity.Err(8002, "令牌参数缺失")
	}

	LoginServerLock.RLock()         //加读锁
	defer LoginServerLock.RUnlock() //解读锁

	loginUser, ok := mapLoginUser[token]
	if !ok {
		return msgentity.Err(8001, "令牌已失效,请重新登录")
	}

	loginUser.LastDate = time.Now().Unix()

	result := loginUser.Clone()
	result.Pass = ""     //必须清除
	result.PassWork = "" //必须清除

	return msgentity.Success(result, "找到令牌对应的登录信息")
}

/**
 * 取登录信息(内部调用)
 * @param token
 * @return
 */
func (ls LoginServer) GetLoginInside(token string) *msgentity.MsgEntity {
	if strings.TrimSpace(token) == "" {
		return msgentity.Err(8002, "令牌参数缺失")
	}

	LoginServerLock.RLock()         //加读锁
	defer LoginServerLock.RUnlock() //解读锁

	loginUser, ok := mapLoginUser[token]
	if !ok {
		return msgentity.Err(8001, "令牌已失效,请重新登录")
	}

	loginUser.LastDate = time.Now().Unix()

	result := loginUser.CloneToMap()
	result["pass"] = ""     //必须清除
	result["passWork"] = "" //必须清除

	return msgentity.Success(result, "找到令牌对应的登录信息")
}

/**
 * 根据用户和密码取对应的用户编号
 * @param nameOrNo
 * @param pass
 * @param owner 用户来源表
 * @return
 */
func (ls LoginServer) GetUserId(nameOrNo, pass, owner string) *msgentity.MsgEntity {
	if strings.TrimSpace(nameOrNo) == "" {
		return msgentity.Err(8000, "登录名不能为空！")
	}

	if strings.TrimSpace(pass) == "" {
		return msgentity.Err(8001, "密码不能为空！")
	}

	if strings.TrimSpace(owner) == "" {
		return msgentity.Err(8007, "用户来源不能为空！")
	}

	me := ls.findBynameOrNo(nameOrNo, owner)
	if !me.Success {
		return msgentity.Err(8002, "查询失败，请稍后重试！")
	}

	list := me.Data.([]LoginUser)

	if len(list) < 1 {
		return msgentity.Err(8003, "相关登录名的用户不存在！")
	}

	if len(list) != 1 {
		return msgentity.Err(8004, "存在重名用户，请使用工号登录！")
	}

	loginUser := list[0]
	if md5util.Lower(pass, appMd5Key) == loginUser.Pass {
		Log.Debug("用户“", loginUser.Name, "”登录时密码错误")
		return msgentity.Err(8005, "用户密码错误！")
	}

	if loginUser.State != 1 {
		return msgentity.Err(8006, "登录受限，请联系管理员！")
	}

	return msgentity.Success(loginUser.Id, "查询成功")
}

/**
 * 检查超时的登录用户,超时则剔除集合
 */
func (ls LoginServer) checkOutTime() {
	if dLastClearTime == 0 {
		dLastClearTime = time.Now().Unix()
		return
	}

	//--判断是否需要进行清理,注意:这个算法并不严谨,但判断用户超时并不需要很严谨的进行时刻清理--//
	vNow := time.Now().Unix()
	if (vNow - dLastClearTime) < serverSessionTimeout {
		return
	}

	LoginServerLock.RLock()         //加读锁
	defer LoginServerLock.RUnlock() //解读锁

	effectiveMap := map[string]*LoginUser{}
	for key, loginUser := range mapLoginUser {
		if (vNow - loginUser.LastDate) < serverSessionTimeout {
			effectiveMap[key] = loginUser
			continue
		}

		//delete(mapLoginUser, key)
		Log.Debug("登录用户'", loginUser.Id, "'因超时将被剔除登录集合!")
	}

	if len(mapLoginUser) == len(effectiveMap) {
		dLastClearTime = time.Now().Unix() //更新最后清理时间
		return
	}

	LoginServerLock.Lock()      //加锁
	mapLoginUser = effectiveMap //替换成有效的
	LoginServerLock.Unlock()    //解锁

	ls.changeCache() //修改缓存

	dLastClearTime = time.Now().Unix() //更新最后清理时间
}

/**
 * 取用户名或工号对应的用户集合
 * @param nameOrNo
 * @param owner
 * @return
 */
func (ls LoginServer) findBynameOrNo(nameOrNo, owner string) *msgentity.MsgEntity {
	return LoginDao{}.FindBynameOrNo(nameOrNo, owner)
}

/**
 * 引入缓存
 */
func (ls LoginServer) loadCache() {
	filePath := "./temp/cache/Login/LoginCache.txt"
	bl, _, data := fileutil.ReadFromFile(filePath)
	if !bl {
		return
	}

	LoginServerLock.Lock()         //加锁
	defer LoginServerLock.Unlock() //解锁

	txt := data.(string)
	err := json.Unmarshal([]byte(txt), &mapLoginUser)
	if err != nil {
		return
	}
}

/**
 * 修改缓存
 */
func (ls LoginServer) changeCache() {
	if len(mapLoginUser) < 1 {
		return
	}

	LoginServerLock.RLock()         //加读锁
	defer LoginServerLock.RUnlock() //解读锁

	ret_json, _ := json.Marshal(mapLoginUser)
	txt := string(ret_json)
	filePath := "./temp/cache/Login/LoginCache.txt"

	go func() {
		LoginServerLock.Lock()       //加锁
		fileutil.Save(txt, filePath) //保存文件
		LoginServerLock.Unlock()     //解锁
	}()
}
